package com.jt.resource.aspect;


import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.jt.pojo.Log;
import com.jt.resource.annotation.RequiredLog;
import com.jt.service.RemoteLogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * 由这个注解描述的类型为一个切面类型，在此类型中可以定义：
 * 1.切入点（切入扩展逻辑的位置~例如权限控制，日志记录，事务的处理）
 * @Aspect描述的类中，通常使用@Pointcut注解进行定义
 * 2.通知方法（在切入点对应得目标方法执行前后要执行逻辑需要写到这样的方法中）
 * @Aspect描述的类中，通过@Before，@After，@Around，@AfterReturning，@AfterThrowing
 * 这样的注解进行描述
 * a:@Before切入点方法执行之前执行
 * b：@After 切入点方法执行之后执行（不管切入点方法是否执行成功，他都会执行）
 * c:@Around 切入点方法执行前后都会执行（最重要）
 * d:@AfterReturning 切入点方法成功执行之后执行
 * e:@AfterThrowing 切入点方法执行出现异常会执行
 *
 */
@Aspect
@Component
public class LogAspect {
    @Autowired
    private RemoteLogService remoteLogService;
    /**
     * @Pointcut 注解用于定义切入点，次注解中的内容为切入点表达式
     *
     * @annotation 为注解方式的切入点表达式，此方式的表达式为一种细粒度的切入点表达式
     * 因为他可以精确到方法，例如我们现在使用RequiredLog注解描述方法时，由他描述的方法
     * 就是一个切入点方法
     */
    @Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
    public void doLog(){
        //此方法中不需要写任何内容，只负责承载@Pointcut
    }

    /**
     * 注解描述的方法为Aspect中的一个环绕通知方法，在此方法中
     * 可以控制对目标方法的调用
     * @param jp 链接点对象，此对象封装了你要执行的切入点方法信息，可以基于此对象对切入点方法进行反射调用
     * @return 目标执行链中切入点方法的返回值
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp)throws Throwable{
        //1.获取目标对象类型
        Class<?> targetClass = jp.getTarget().getClass();
        //2.获取目标方法
        //2.1.获取方法签名（）
        MethodSignature signature = (MethodSignature)jp.getSignature();
        //2.2 获取方法对象
        Method targetMethod =
        targetClass.getDeclaredMethod(signature.getName(), signature.getMethod().getParameterTypes());
        //3.获取注解
        RequiredLog annotation = targetMethod.getAnnotation(RequiredLog.class);
        //获取注解的value
        String operation = annotation.value();

        Long t1 = System.currentTimeMillis();
        int status = 1;
        String error = "";
        Object proceed = null;
        //手动调用目标执行链(这个执行链中包含切入点方法~目标方法)
        try {
            proceed = jp.proceed();
        }catch (Throwable e){
            status=0;
            error=e.getMessage();
            throw e;
        }

        Long t2 = System.currentTimeMillis();
        //后续可以通过feign将获取到的日志
        //将获取的用户日志封装到Log对象
        //将Log对象通过Feign或者resttenplate发送到搜搜——sustem工程中去
        Log logInfo = new Log();
        String username = (String) SecurityContextHolder.getContext()
                .getAuthentication().getPrincipal();
        ServletRequestAttributes requestAttributes =
                (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        String ip = requestAttributes.getRequest().getRemoteAddr();
        logInfo.setIp(ip)
                .setUsername(username)
                .setOperation(operation)
                .setMethod(targetClass.getName()+"."+targetMethod.getName())
                .setParams(new ObjectMapper().writeValueAsString(jp.getArgs()))
                .setTime(t2-t1)
                .setStatus(status)
                .setCreatedTime(new Date())
                .setError(error);
        System.out.println("logInfo="+logInfo);
        //作业:将Log对象通过Feign或者RestTemplate发送到sso-system工程进行日志记录
        remoteLogService.doInsertLog(logInfo);
        return proceed;
    }


}
